/**********************************************************************
 * file:  sr_router.c 
 * date:  Mon Feb 18 12:50:42 PST 2002  
 * Contact: casado@stanford.edu 
 *
 * Description:
 * 
 * This file contains all the functions that interact directly
 * with the routing table, as well as the main entry method
 * for routing.
 *
 **********************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include "sr_if.h"
#include "sr_rt.h"
#include "sr_router.h"
#include "sr_protocol.h"
#include "vnscommand.h"

/* Global variables added by Richard Thatcher and Jared Law. */
struct sr_instance* curr_sr;
uint8_t* curr_packet;
unsigned int curr_len;
struct sr_if* iface;
struct sr_ethernet_hdr* ehdr;
struct sr_arphdr* ahdr;
struct ip* ip;
struct sr_icmp_hdr * icmp;
p_buffer b_head = NULL,
		 b_curr = NULL;
arpCache cache = NULL;
const uint8_t unk_ether[] = {0xff,0xff,0xff,0xff,0xff,0xff};
int debug = 1;
/* End additional global variables. */


/*--------------------------------------------------------------------- 
 * Method: sr_init(void)
 * Scope:  Global
 *
 * Initialize the routing subsystem
 * 
 *---------------------------------------------------------------------*/
void sr_init(struct sr_instance* sr) 
{
    /* REQUIRES */
    assert(sr);

    /* Add initialization code here! */

} /* -- sr_init -- */


/*---------------------------------------------------------------------
 * Method: sr_handlepacket(uint8_t* p,char* interface)
 * Scope:  Global
 *
 * This method is called each time the router receives a packet on the
 * interface.  The packet buffer, the packet length and the receiving
 * interface are passed in as parameters. The packet is complete with
 * ethernet headers.
 *
 * Note: Both the packet buffer and the character's memory are handled
 * by sr_vns_comm.c that means do NOT delete either.  Make a copy of the
 * packet instead if you intend to keep it around beyond the scope of
 * the method call.
 *
 *---------------------------------------------------------------------*/
void sr_handlepacket(struct sr_instance* sr, 
        uint8_t * packet/* lent */,
        unsigned int len,
        char* interface/* lent */)
{
    /* REQUIRES */
    assert(sr);
    assert(packet);
    assert(interface);

    // Set global variables.
    curr_sr = sr;
    curr_packet = packet;
    curr_len = len;
    iface = sr_get_interface(curr_sr, interface);

    // Print packet information.
    if (debug > 1) print_packet();

    // Route current packet.
    b_curr = NULL;
    handleEthernet();

    // Attempt to route outstanding packets in buffer (if any).
    if (b_curr) {
    	b_curr = b_curr->next;
    } else {
    	b_curr = b_head;
    }

    while (b_curr) {
    	curr_packet = b_curr->packet;
    	curr_len = b_curr->len;
    	iface = sr_get_interface(curr_sr, b_curr->interface);
    	handleEthernet();
    	b_curr = b_curr->next;
    }

    // Remove old or sent packets from buffer.
    cleanBuffer();
}/* end sr_ForwardPacket */


void handleEthernet() {
	// Access Ethernet header.
    ehdr = (struct sr_ethernet_hdr*) curr_packet;

    switch(htons(ehdr->ether_type)) {
    case ETHERTYPE_ARP: handleARP(); break;
    case ETHERTYPE_IP: handleIP(); break;
    default: if (debug) printf("\nDropping unknown packet! (%d)\n", curr_len);
    }
}


void handleARP() {
	// Access ARP header.
	ahdr = (struct sr_arphdr*) ((void*) ehdr + sizeof(struct sr_ethernet_hdr));

	// Update ARP cache.
	updateARPCache();

	// If this is an ARP request . . .
	if (ahdr->ar_op == ntohs(ARP_REQUEST)) {
		if (debug == 1)
			printf("\n* * * * * Received ARP request (%d) * * * * *\n", curr_len);

		// Set MAC addresses in Ethernet header.
		memcpy(ehdr->ether_dhost, ehdr->ether_shost, ETHER_ADDR_LEN);
		memcpy(ehdr->ether_shost, iface->addr, ETHER_ADDR_LEN);

		// Set IP addresses in ARP header.
		ahdr->ar_tip = ahdr->ar_sip;
		ahdr->ar_sip = iface->ip;

		// Set MAC addresses in ARP header.
		memcpy(ahdr->ar_tha, ahdr->ar_sha, ETHER_ADDR_LEN);
		memcpy(ahdr->ar_sha, iface->addr, ETHER_ADDR_LEN);

		// Set op code to ARP reply.
		ahdr->ar_op = ntohs(ARP_REPLY);

	    // Return packet.
		if (debug)
			printf("\n* * * * * Sending ARP reply * * * * *\n");
		if (debug > 1) {
			print_eth(ehdr);
			print_arp(ahdr);
		}
	    sr_send_packet(curr_sr, curr_packet, curr_len, iface->name);
	} else {
		if (debug == 1)
			printf("\n* * * * * Received ARP reply (%d) * * * * *\n", curr_len);
	}
}

void handleIP() {
	// Access IP header.
	ip = (struct ip*) ((void*) ehdr + sizeof(struct sr_ethernet_hdr));

	// If this router is the target . . .
	if (isTargetRouter()) {
		switch(ip->ip_p) {
		case 1: handleICMP(); break;
		case 17: handleUDP(); break;
		}

		return;
	}

	// If TTL has expired . . .
	if (ip->ip_ttl <= 1) {
		// Send "Expired TTL" message.
		sendExpiredIP();
		return;
	}

	// If a packet somehow arrives from outside this network that does
	// not target a network address . . .
	if (!isFromNetwork() && !isTargetNetwork()) {
		// Send "Destination Unreachable" message.
		sendUnreachableIP();
		return;
	}

	// Look up interface from routing table.
	getInterface();

	// If ARP cache has a MAC address for interface . . .
	if (getTargetMACAddress(iface->name, ehdr->ether_dhost)) {
		// Set source MAC address.
		memcpy(ehdr->ether_shost, iface->addr, ETHER_ADDR_LEN);

		// Forward packet.
		if (debug)
			printf("\n* * * * * Forwarding IP packet * * * * *\n");
		if (debug > 1) {
			print_eth(ehdr);
			print_ip(ip);
		}
		sr_send_packet(curr_sr, curr_packet, curr_len, iface->name);

		// If packet came from buffer . . .
		if (b_curr) {
			// Mark packet for removal from buffer.
			b_curr->count = 0;
		}

		return;
	}

	// If packet is not buffered, add it.
	if (!b_curr) addToBuffer();

	// Send an ARP request.
	sendARP();

	// Increment packet ARP count.
	b_curr->count++;
}


void handleICMP() {
	// Access ICMP header.
	icmp = (struct sr_icmp_hdr*) ((void*) ip + sizeof(struct ip));
	if (debug == 1)
		printf("\n* * * * * Received ICMP packet (%d) * * * * *\n", curr_len);

	// If this is an echo request . . .
	if (icmp->type == ECHO_REQUEST) {
		// Set MAC addresses in Ethernet header.
		memcpy(ehdr->ether_dhost, ehdr->ether_shost, ETHER_ADDR_LEN);
		memcpy(ehdr->ether_shost, iface->addr, ETHER_ADDR_LEN);

		// Set IP addresses in IP header.
		ip->ip_dst = ip->ip_src;
		ip->ip_src.s_addr = iface->ip;

		// Set IP codes.
		ip->ip_ttl = 255;

		// Set IP checksum.
		ip->ip_sum = 0;
		ip->ip_sum = checksum((uint16_t*) ip, sizeof(struct ip));

		// Set ICMP codes.
		icmp->type = ECHO_REPLY;
		icmp->code = 0;

		// Set ICMP checksum.
		icmp->sum = 0;
		icmp->sum = checksum((uint16_t*) icmp, curr_len - sizeof(struct sr_ethernet_hdr) - sizeof(struct ip));

		// Return packet.
		if (debug)
			printf("\n* * * * * Sending ICMP packet * * * * *\n");
		if (debug > 1) {
			print_eth(ehdr);
			print_ip(ip);
			print_icmp(icmp);
		}
		sr_send_packet(curr_sr, curr_packet, curr_len, iface->name);
	} else {
		if (debug)
			printf("\nDropping unknown ICMP type packet!\n");
	}
}


/* Any UDP received by this router gets a "Port Unreachable" message
 * in return.
 */
void handleUDP() {
	int len = sizeof(struct sr_ethernet_hdr) +
			  sizeof(struct ip) +
			  sizeof(struct sr_icmp_hdr) +
			  (4 * (ip->ip_hl)) + 8;
	uint8_t* tmp_packet = malloc(len);
	struct sr_ethernet_hdr* tmp_ehdr = (struct sr_ethernet_hdr*) tmp_packet;
	struct ip* tmp_ip = (struct ip*) ((void*) tmp_ehdr + sizeof(struct sr_ethernet_hdr));
	struct sr_icmp_hdr* tmp_icmp = (struct sr_icmp_hdr*) ((void*) tmp_ip + sizeof(struct ip));

	// Display header information (if it's a new packet).
	if (debug == 1)
		printf("\n* * * * * Received UDP packet (%d) * * * * *\n", curr_len);

	// Set MAC addresses in Ethernet header.
	memcpy(tmp_ehdr->ether_dhost, ehdr->ether_shost, ETHER_ADDR_LEN);
	memcpy(tmp_ehdr->ether_shost, ehdr->ether_dhost, ETHER_ADDR_LEN);
	tmp_ehdr->ether_type = ntohs(ETHERTYPE_IP);

	// Set IP header data.
	tmp_ip->ip_hl = 5;
	tmp_ip->ip_v = 4;
	tmp_ip->ip_len = ntohs(len - sizeof(struct sr_ethernet_hdr));
	tmp_ip->ip_ttl = 255;
	tmp_ip->ip_p = 1;
	tmp_ip->ip_dst = ip->ip_src;
	tmp_ip->ip_src.s_addr = iface->ip;
	tmp_ip->ip_sum = 0;
	tmp_ip->ip_sum = checksum((uint16_t*) tmp_ip, sizeof(struct ip));

	// Set ICMP header data.
	tmp_icmp->type = 3;
	tmp_icmp->code = 3;
	tmp_icmp->rest = 0;
	tmp_icmp->sum = 0;
	tmp_icmp->sum = checksum((uint16_t*) tmp_icmp, sizeof(struct sr_icmp_hdr) + sizeof(struct ip) + 8);

	// Copy authentication data.
	uint8_t* data = (uint8_t*) ((void *) tmp_icmp + sizeof(struct sr_icmp_hdr));
	memcpy(data, ip, sizeof(struct ip) + 8);
	ip = (struct ip*) data;

	// Return packet.
	if (debug)
		printf("\n* * * * * Sending \"Port Unreachable\" ICMP packet (%d) * * * * *\n", len);
	if (debug > 1) {
		print_eth(tmp_ehdr);
		print_ip(tmp_ip);
		print_icmp(tmp_icmp);
		print_ip(ip);
	}
	sr_send_packet(curr_sr, tmp_packet, len, iface->name);

	// Clear temporary packet.
	free(tmp_packet);
}


void sendARP() {
	int len = sizeof(struct sr_ethernet_hdr) + sizeof(struct sr_arphdr);
	uint8_t* arp = malloc(len);

	// Look up interface from routing table.
	getInterface();

	// Setup Ethernet header.
	ehdr = (struct sr_ethernet_hdr*) arp;
	memcpy(ehdr->ether_shost, iface->addr, ETHER_ADDR_LEN);
	memcpy(ehdr->ether_dhost, unk_ether, ETHER_ADDR_LEN);
	ehdr->ether_type = ntohs(ETHERTYPE_ARP);

	// Setup ARP header.
	ahdr = (struct sr_arphdr*) ((void*) ehdr + sizeof(struct sr_ethernet_hdr));
	ahdr->ar_hrd = 256;
	ahdr->ar_pro = 8;
	ahdr->ar_hln = ETHER_ADDR_LEN;
	ahdr->ar_pln = 4;
	ahdr->ar_op = ntohs(ARP_REQUEST);
	memcpy(ahdr->ar_sha, iface->addr, ETHER_ADDR_LEN);
	ahdr->ar_sip = iface->ip;
	memcpy(ahdr->ar_tha, unk_ether, ETHER_ADDR_LEN);
	ahdr->ar_tip = ip->ip_dst.s_addr;

	// Send ARP packet.
	if (debug)
		printf("\n* * * * * Sending ARP request * * * * *\n");
    if (debug > 1) {
    	print_eth(ehdr);
    	print_arp(ahdr);
    }
	sr_send_packet(curr_sr, arp, 42, iface->name);

	// Increment count in packet buffer.
	b_curr->count++;

	// Clear memory.
	free(arp);
}


void sendExpiredIP() {
	int len = sizeof(struct sr_ethernet_hdr) +
			  sizeof(struct ip) +
			  sizeof(struct sr_icmp_hdr) +
			  (4 * (ip->ip_hl)) + 8;
	uint8_t* tmp_packet = malloc(len);
	struct sr_ethernet_hdr* tmp_ehdr = (struct sr_ethernet_hdr*) tmp_packet;
	struct ip* tmp_ip = (struct ip*) ((void*) tmp_ehdr + sizeof(struct sr_ethernet_hdr));
	struct sr_icmp_hdr* tmp_icmp = (struct sr_icmp_hdr*) ((void*) tmp_ip + sizeof(struct ip));

	// Display header information (if it's a new packet).
	if (debug == 1)
		printf("\n* * * * * Received Expired IP packet (%d) * * * * *\n", curr_len);

	// Set MAC addresses in Ethernet header.
	memcpy(tmp_ehdr->ether_dhost, ehdr->ether_shost, ETHER_ADDR_LEN);
	memcpy(tmp_ehdr->ether_shost, ehdr->ether_dhost, ETHER_ADDR_LEN);
	tmp_ehdr->ether_type = ntohs(ETHERTYPE_IP);

	// Set IP header data.
	tmp_ip->ip_hl = 5;
	tmp_ip->ip_v = 4;
	tmp_ip->ip_len = ntohs(len - sizeof(struct sr_ethernet_hdr));
	tmp_ip->ip_ttl = 255;
	tmp_ip->ip_p = 1;
	tmp_ip->ip_dst = ip->ip_src;
	tmp_ip->ip_src.s_addr = iface->ip;
	tmp_ip->ip_sum = 0;
	tmp_ip->ip_sum = checksum((uint16_t*) tmp_ip, sizeof(struct ip));

	// Set ICMP header data.
	tmp_icmp->type = 11;
	tmp_icmp->code = 0;
	tmp_icmp->rest = 0;
	tmp_icmp->sum = 0;
	tmp_icmp->sum = checksum((uint16_t*) tmp_icmp, sizeof(struct sr_icmp_hdr) + sizeof(struct ip) + 8);

	// Copy authentication data.
	uint8_t* data = (uint8_t*) ((void *) tmp_icmp + sizeof(struct sr_icmp_hdr));
	memcpy(data, ip, sizeof(struct ip) + 8);
	ip = (struct ip*) data;

	// Return packet.
	if (debug)
		printf("\n* * * * * Sending \"Expired\" ICMP packet * * * * *\n");
	if (debug > 1) {
		print_eth(tmp_ehdr);
		print_ip(tmp_ip);
		print_icmp(tmp_icmp);
		print_ip(ip);
	}
	sr_send_packet(curr_sr, tmp_packet, len, iface->name);

	// Clear temporary packet.
	free(tmp_packet);
}


void sendUnreachableIP() {
	int len = sizeof(struct sr_ethernet_hdr) +
			  sizeof(struct ip) +
			  sizeof(struct sr_icmp_hdr) +
			  (4 * (ip->ip_hl)) + 8;
	uint8_t* tmp_packet = malloc(len);
	struct sr_ethernet_hdr* tmp_ehdr = (struct sr_ethernet_hdr*) tmp_packet;
	struct ip* tmp_ip = (struct ip*) ((void*) tmp_ehdr + sizeof(struct sr_ethernet_hdr));
	struct sr_icmp_hdr* tmp_icmp = (struct sr_icmp_hdr*) ((void*) tmp_ip + sizeof(struct ip));

	// Display header information (if it's a new packet).
	if (debug == 1)
		printf("\n* * * * * Received Unreachable IP packet (%d) * * * * *\n", curr_len);

	// Set MAC addresses in Ethernet header.
	memcpy(tmp_ehdr->ether_dhost, ehdr->ether_shost, ETHER_ADDR_LEN);
	memcpy(tmp_ehdr->ether_shost, ehdr->ether_dhost, ETHER_ADDR_LEN);
	tmp_ehdr->ether_type = ntohs(ETHERTYPE_IP);

	// Set IP header data.
	tmp_ip->ip_hl = 5;
	tmp_ip->ip_v = 4;
	tmp_ip->ip_len = ntohs(len - sizeof(struct sr_ethernet_hdr));
	tmp_ip->ip_ttl = 255;
	tmp_ip->ip_p = 1;
	tmp_ip->ip_dst = ip->ip_src;
	tmp_ip->ip_src.s_addr = iface->ip;
	tmp_ip->ip_sum = 0;
	tmp_ip->ip_sum = checksum((uint16_t*) tmp_ip, sizeof(struct ip));

	// Set ICMP header data.
	tmp_icmp->type = 3;
	tmp_icmp->code = 1;
	tmp_icmp->rest = 0;
	tmp_icmp->sum = 0;
	tmp_icmp->sum = checksum((uint16_t*) tmp_icmp, sizeof(struct sr_icmp_hdr));

	// Copy authentication data.
	uint8_t* data = (uint8_t*) ((void *) tmp_icmp + sizeof(struct sr_icmp_hdr));
	memcpy(data, ip, sizeof(struct ip) + 8);
	ip = (struct ip*) data;

	// Return packet.
	if (debug)
		printf("\n* * * * * Sending \"Unreachable\" ICMP packet * * * * *\n");
	if (debug > 1) {
		print_eth(tmp_ehdr);
		print_ip(tmp_ip);
		print_icmp(tmp_icmp);
		print_ip(ip);
	}
	sr_send_packet(curr_sr, tmp_packet, len, iface->name);

	// Clear temporary packet.
	free(tmp_packet);
}


/* Checks the routing table for the destination IP address.
 * Return 1 - IP address is in routing table
 * Return 0 - IP address is not in routing table
 */
int isTargetNetwork() {
	struct sr_rt* rt = curr_sr->routing_table;
	while (rt) {
		if (rt->dest.s_addr == ip->ip_dst.s_addr)
			return 1;

		rt = rt->next;
	}

	return 0;
}


/* Checks to see if this router is the target.
 * Return 1 - Destination IP address matches interface IP address.
 * Return 0 - Destination IP address does not match interface IP address.
 */
int isTargetRouter() {
	return ip->ip_dst.s_addr == iface->ip;
}


/* Determines if the packet was sent from a host on this network by
 * searching the routing table for the source IP address.
 * Return 1 - Source IP address is in routing table.
 * Return 0 - Source IP address is not in routing table.
 */
int isFromNetwork() {
	struct sr_rt* rt = curr_sr->routing_table;
	while (rt) {
		if (rt->dest.s_addr == ip->ip_src.s_addr)
			return 1;

		rt = rt->next;
	}

	return 0;
}


/* Gets the interface from the routing table using the destination
 * IP address. If the routing table does not contain the IP address,
 * the default (internet) interface is used.
 */
void getInterface() {
	// Search routing table for IP address.
	struct sr_rt* rt = curr_sr->routing_table;
	while (rt && rt->dest.s_addr != ip->ip_dst.s_addr)
		rt = rt->next;

	// If the IP address was not found . . .
	if (!rt) {
		// Search for the default (internet) IP address.
		rt = curr_sr->routing_table;
		while (rt && rt->dest.s_addr != 0)
			rt = rt->next;
	}

	// Access interface for target IP address.
	iface = sr_get_interface(curr_sr, rt->interface);
}


void updateARPCache() {
	int found = 0;
	arpCache tmp;

	tmp = cache;
	while (tmp && !found) {
		if (ahdr->ar_sip == tmp->ip) {
			found = 1;
		}

		tmp = tmp->next;
	}

	if (!found) {
		tmp = malloc(sizeof(struct arp_cache));
		tmp->ip = ahdr->ar_sip;
		memcpy(tmp->ha, ahdr->ar_sha, ETHER_ADDR_LEN);
		memcpy(tmp->interface, iface->name, 32);
		tmp->next = cache;
		cache = tmp;
	}
}


int getTargetMACAddress(char* interface, uint8_t* mac) {
	arpCache tmp;
	int found = 0;

	tmp = cache;
	while (tmp && !found) {
		if(!strncmp(interface, tmp->interface, 32)) {
			found = 1;
			memcpy(mac, tmp->ha, ETHER_ADDR_LEN);
		}

		tmp = tmp->next;
	}

	return found;
}


void addToBuffer() {
	p_buffer tmp_p = malloc(sizeof(struct packet_buffer));
	tmp_p->packet = malloc(sizeof(uint8_t)*curr_len);
	memcpy(tmp_p->packet, curr_packet, sizeof(uint8_t)*curr_len);
	tmp_p->len = curr_len;
	memcpy(tmp_p->interface, iface->name, 32);
	tmp_p->count = 0;

	tmp_p->next = b_head;
	b_head = tmp_p;
	b_curr = tmp_p;
}


void cleanBuffer() {
	p_buffer prev = NULL,
			 erase = NULL;
	b_curr = b_head;
	while (b_curr) {
		if (b_curr->count > 5) {
			// Send some kind of failure to connect message.
		}

		if (b_curr->count > 5 || b_curr->count < 1) {
			erase = b_curr;

			if (b_curr == b_head) {
				b_head = b_curr->next;
			} else {
				prev->next = b_curr->next;
			}
		} else {
			erase = NULL;
			prev = b_curr;
		}

		b_curr = b_curr->next;

		if (erase) {
			free(erase->packet);
			free(erase);
		}
	}
}


//Calculates the checksum
uint16_t checksum(uint16_t *p_Data, int nLength)
{
	uint32_t nReturn = 0;
	int nIndex = 0;

	for (nIndex = 0; nIndex < nLength / 2; nIndex++)
		nReturn += formatNumber_16(*(p_Data + nIndex));

	if (nLength % 2 != 0)
		nReturn += formatNumber_16(*(p_Data + nIndex)) & 0xFF00;

	return formatNumber_16(~((nReturn >> 16) + (nReturn & 0xFFFF)));
}


uint16_t formatNumber_16(uint16_t nValue)
{
	return ((nValue << 8) & 0xFF00) + (nValue >> 8);
}


void print_iface(struct sr_if* iface) {
	printf("--- Interface ---\n");
	sr_print_if(iface);
}


void print_eth(struct sr_ethernet_hdr* hdr) {
	printf("--- Ethernet Header ---\n");
	printf("Destination Address:\t");
	DebugMAC(hdr->ether_dhost);
	printf("\n");
	printf("Source Address:\t\t");
	DebugMAC(hdr->ether_shost);
	printf("\n");
	printf("Type:\t\t\t");
	switch(htons(hdr->ether_type)) {
	case ETHERTYPE_IP: printf("IP\n"); break;
	case ETHERTYPE_ARP: printf("ARP\n"); break;
	default: printf("unknown\n");
	}
}


void print_arp(struct sr_arphdr* hdr) {
	union b_value tmp;
	printf("--- ARP Header ---\n");
	printf("Hardware Address Format:\t%d\n", hdr->ar_hrd);
	printf("Protocol Address Format:\t%d\n", hdr->ar_pro);
	printf("Hardware Address Length:\t%d\n", hdr->ar_hln);
	printf("Protocol Address Length:\t%d\n", hdr->ar_pln);
	printf("Opcode:\t\t\t\t");
	switch (htons(hdr->ar_op)) {
	case ARP_REQUEST:	printf("ARP Request"); break;
	case ARP_REPLY:		printf("ARP Reply"); break;
	}
	printf("\n");
	printf("Source Hardware Address:\t");
	DebugMAC(hdr->ar_sha);
	printf("\n");
	tmp.val = hdr->ar_sip;
	printf("Source IP Address:\t\t%d.%d.%d.%d\n", tmp.bf1.a1, tmp.bf1.a2, tmp.bf1.a3, tmp.bf1.a4);
	printf("Target Hardware Address:\t");
	DebugMAC(hdr->ar_tha);
	printf("\n");
	tmp.val = hdr->ar_tip;
	printf("Target IP Address:\t\t%d.%d.%d.%d\n", tmp.bf1.a1, tmp.bf1.a2, tmp.bf1.a3, tmp.bf1.a4);
}


void print_ip(struct ip* hdr) {
	printf("--- IP Header ---\n");
	printf("Header length:\t\t%d\n", hdr->ip_hl);
	printf("Version:\t\t%d\n", hdr->ip_v);
	printf("Type of service:\t%d\n", hdr->ip_tos);
	printf("Total length:\t\t%d\n", htons(hdr->ip_len));
	printf("ID:\t\t\t%d\n", hdr->ip_id);
	printf("Offset:\t\t\t%d\n", hdr->ip_off);
	printf("Time to live:\t\t%d\n", hdr->ip_ttl);
	printf("Protocol:\t\t%d\n", hdr->ip_p);
	printf("Checksum:\t\t%d\n", hdr->ip_sum);
	printf("Source address:\t\t%s\n", inet_ntoa(hdr->ip_src));
	printf("Destination address:\t%s\n", inet_ntoa(hdr->ip_dst));
}


void print_icmp(struct sr_icmp_hdr* icmp) {
	printf("--- ICMP Header ---\n");
	printf("Type: %d\n", icmp->type);
	printf("Code: %d\n", icmp->code);
	printf("Checksum: %x\n", icmp->sum);
	printf("What's left: %d\n", icmp->rest);
}


void print_packet() {
	printf("\n* * * * * Received packet of length %d. * * * * *\n", curr_len);

	// Access Ethernet header.
    ehdr = (struct sr_ethernet_hdr*) curr_packet;
    print_eth(ehdr);

    switch(htons(ehdr->ether_type)) {
    case ETHERTYPE_ARP: {
    	// Access ARP header.
    	ahdr = (struct sr_arphdr*) ((void*) ehdr + sizeof(struct sr_ethernet_hdr));
    	print_arp(ahdr);
    	break;
    }
    case ETHERTYPE_IP: {
    	// Access IP header.
    	ip = (struct ip*) ((void*) ehdr + sizeof(struct sr_ethernet_hdr));
    	print_ip(ip);
    	switch(ip->ip_p) {
    	case 1: {
    		// Access ICMP header.
    		icmp = (struct sr_icmp_hdr*) ((void*) ip + sizeof(struct ip));
    		print_icmp(icmp);
    		if (icmp->type == 3 || icmp->type == 11) {
    			// Access IP header.
    			ip = (struct ip*) ((void*) icmp + sizeof(struct sr_icmp_hdr));
    			print_ip(ip);

    			// Access ICMP header.
    			icmp = (struct sr_icmp_hdr*) ((void*) ip + sizeof(struct ip));
    			print_icmp(icmp);
    		}
    		break;
    	}
    	case 17: break;
    	}
    	break;
    }
    default: if (debug) printf("\nDropping unknown packet! (%d)\n", curr_len);
    }
}
